use std::{
    alloc::{self, Layout},
    mem,
    ptr::NonNull,
};

use log::trace;
use page_table_generic::*;

use tock_registers::{interfaces::*, register_bitfields, registers::*};

const MB: usize = 1024 * 1024;
const GB: usize = 1024 * MB;

register_bitfields! [
    u64,
    PTE [
        PA OFFSET(0) NUMBITS(48) [
        ],
        READ OFFSET(48) NUMBITS(1) [
        ],
        WRITE OFFSET(49) NUMBITS(1) [
        ],
        USER_EXECUTE OFFSET(50) NUMBITS(1) [
        ],
        USER_ACCESS OFFSET(51) NUMBITS(1) [
        ],
        PRIVILEGE_EXECUTE OFFSET(52) NUMBITS(1) [
        ],
        BLOCK OFFSET(53) NUMBITS(1) [
        ],
        CACHE OFFSET(54) NUMBITS(2) [
            NonCache = 0,
            Normal = 0b01,
            Device = 0b10,
        ],
        VALID OFFSET(63) NUMBITS(1) [

        ]
    ],
];

#[derive(Clone, Copy)]
struct PteImpl;

impl PTEArch for PteImpl {
    fn page_size() -> usize {
        4096
    }

    fn new_pte(config: PTEGeneric) -> usize {
        let paddr = config.paddr >> 12;
        let mut v = PTE::PA.val(paddr as _);
        v += PTE::VALID::SET;

        if config.setting.privilege_access.readable() {
            v += PTE::READ::SET;
        }

        if config.setting.privilege_access.writable() {
            v += PTE::WRITE::SET;
        }

        if config.setting.user_access.readable() | config.setting.user_access.writable() {
            v += PTE::USER_ACCESS::SET;
        }
        if config.setting.privilege_access.executable() {
            v += PTE::PRIVILEGE_EXECUTE::SET;
        }

        v += match config.setting.cache_setting {
            CacheSetting::Normal => PTE::CACHE::Normal,
            CacheSetting::Device => PTE::CACHE::Device,
            CacheSetting::NonCache => PTE::CACHE::NonCache,
        };

        if config.is_block {
            v += PTE::BLOCK::SET;
        }

        v.value as _
    }

    fn read_pte(pte: usize) -> PTEGeneric {
        let mut g = PTEGeneric::default();
        let v: ReadWrite<u64, PTE::Register> = unsafe { mem::transmute(pte) };
        g.is_valid = v.is_set(PTE::VALID);
        if !g.is_valid {
            return g;
        }

        let paddr: u64 = v.read(PTE::PA) << 12;
        g.paddr = paddr as usize;

        if v.is_set(PTE::READ) {
            g.setting.privilege_access |= AccessSetting::Read;
        }
        if v.is_set(PTE::WRITE) {
            g.setting.privilege_access |= AccessSetting::Write;
        }
        if v.is_set(PTE::USER_ACCESS) {
            g.setting.user_access |= AccessSetting::Read;

            if v.is_set(PTE::WRITE) {
                g.setting.user_access |= AccessSetting::Write;
            }
        }
        if v.is_set(PTE::USER_EXECUTE) {
            g.setting.user_access |= AccessSetting::Execute;
        }
        if v.is_set(PTE::PRIVILEGE_EXECUTE) {
            g.setting.privilege_access |= AccessSetting::Execute;
        }
        g.setting.cache_setting = match v.read_as_enum(PTE::CACHE) {
            Some(PTE::CACHE::Value::Normal) => CacheSetting::Normal,
            Some(PTE::CACHE::Value::Device) => CacheSetting::Device,
            Some(PTE::CACHE::Value::NonCache) => CacheSetting::NonCache,
            None => panic!("invalid cache setting"),
        };

        g.is_block = v.is_set(PTE::BLOCK);
        g
    }

    fn level() -> usize {
        4
    }
}

struct AccessImpl;

impl Access for AccessImpl {
    fn va_offset(&self) -> usize {
        0
    }

    unsafe fn alloc(&mut self, layout: Layout) -> Option<NonNull<u8>> {
        let ptr = alloc::alloc(layout);
        trace!("alloc: {:?}", ptr);
        NonNull::new(ptr)
    }

    unsafe fn dealloc(&mut self, mut ptr: NonNull<u8>, layout: Layout) {
        trace!("dealloc: {:?}", ptr);
        alloc::dealloc(ptr.as_mut(), layout);
    }
}

#[test]
fn test_pte() {
    let want = PTEGeneric {
        paddr: 0xfff123456780000,
        is_block: false,
        setting: PTESetting {
            cache_setting: CacheSetting::Normal,
            privilege_access: AccessSetting::empty(),
            user_access: AccessSetting::empty(),
            is_global: false,
        },
        is_valid: true,
    };

    let v = PteImpl::new_pte(want.clone());
    let pte = PteImpl::read_pte(v);

    assert_eq!(want, pte);
}

#[test]
fn test_new() {
    let _ = env_logger::builder()
        .is_test(true)
        .filter_level(log::LevelFilter::Trace)
        .try_init();

    let mut access = AccessImpl;

    let mut pg = PageTableRef::<PteImpl>::create_empty(&mut access).unwrap();
    unsafe {
        pg.map_region(
            MapConfig::new(
                0xffff000000000000usize as _,
                0x0000,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            ),
            0x2000,
            false,
            &mut access,
        )
        .unwrap();
    }
    let msg = pg
        .as_slice(&access)
        .iter()
        .filter_map(|o| {
            let v = o.as_usize();
            if v > 0 {
                Some(format!("{:#x}", v))
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join(", ");

    println!("vec: {}", msg);

    let list = pg.iter_all(&access).collect::<Vec<_>>();

    for i in &list {
        println!("l: {:x}, va: {:#p} c: {:?}", i.level, i.vaddr, i.pte);
    }

    assert_eq!(list.len(), 5);
}

#[test]
fn test_block() {
    let _ = env_logger::builder()
        .is_test(true)
        .filter_level(log::LevelFilter::Trace)
        .try_init();

    let mut access = AccessImpl;

    let mut pg = PageTableRef::<PteImpl>::create_empty(&mut access).unwrap();
    unsafe {
        pg.map_region(
            MapConfig::new(
                0xff0000000000usize as _,
                0x80000000,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            ),
            2 * GB,
            true,
            &mut access,
        )
        .unwrap();
    }
    let msg = pg
        .as_slice(&access)
        .iter()
        .filter_map(|o| {
            let v = o.as_usize();
            if v > 0 {
                Some(format!("{:#x}", v))
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join(", ");

    println!("vec: {}", msg);

    let list = pg.iter_all(&access).collect::<Vec<_>>();

    for i in &list {
        println!("l: {:x}, va: {:#p} c: {:?}", i.level, i.vaddr, i.pte);
    }

    // assert_eq!(list.len(), 2);
    assert!(list.last().unwrap().pte.is_block);
}

#[test]
fn test_release() {
    let _ = env_logger::builder()
        .is_test(true)
        .filter_level(log::LevelFilter::Trace)
        .try_init();

    let mut access = AccessImpl;

    let mut pg = PageTableRef::<PteImpl>::create_empty(&mut access).unwrap();
    unsafe {
        pg.map_region(
            MapConfig::new(
                0xffff000000000000usize as _,
                0x0000,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            )
            .set_user_access(AccessSetting::Read),
            0x2000,
            false,
            &mut access,
        )
        .unwrap();
    }
    for i in pg.iter_all(&access) {
        println!("l: {:x}, va: {:#p} c: {:?}", i.level, i.vaddr, i.pte);
    }
    pg.release(&mut access);
}

#[test]
fn test_2() {
    let _ = env_logger::builder()
        .is_test(true)
        .filter_level(log::LevelFilter::Trace)
        .try_init();

    let mut access = AccessImpl;

    let mut pg = PageTableRef::<PteImpl>::create_empty(&mut access).unwrap();
    unsafe {
        pg.map_region(
            MapConfig::new(
                0xffffff0000000000usize as _,
                0x0,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            )
            .set_user_access(AccessSetting::Read),
            0x3b400000,
            true,
            &mut access,
        )
        .unwrap();
        pg.map_region(
            MapConfig::new(
                0xffffff007d500000usize as _,
                0x7d500000,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            )
            .set_user_access(AccessSetting::Read),
            0xa000,
            true,
            &mut access,
        )
        .unwrap();
        pg.map_region(
            MapConfig::new(
                0xffffff007d500000usize as _,
                0x7d500000,
                AccessSetting::Read | AccessSetting::Write,
                CacheSetting::Device,
            )
            .set_user_access(AccessSetting::Read),
            0xa000,
            true,
            &mut access,
        )
        .unwrap();
    }

    // for i in pg.iter_all(&access) {
    //     println!("l: {:x}, va: {:#p} c: {:?}", i.level, i.vaddr, i.pte);
    // }
    pg.release(&mut access);
}
